using SharpSoft.Net.Protocols;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace SharpSoft.Net.P2P
{
    public class TcpPeer : IPeer
    {
        private Socket peersocket;
        private NetworkStream netstream;

        public event EventHandler Disconnected;
        protected bool isworking = false;

        public Socket Socket
        {
            get => peersocket;
            private set
            {
                peersocket = value;
            }
        }

        public TcpPeer()
        {
            Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); 
            //Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
            //Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true);
        }
        public TcpPeer(Socket socket)
        {
            Socket = socket;
            isworking = true;
        }
        public TcpPeer(int localport) : this(IPAddress.Any, localport)
        {

        }
        public TcpPeer(IPEndPoint localep) : this()
        {
            this.Bind(localep);
        }
        public TcpPeer(string localhost, int localport) : this(new IPEndPoint(IPAddress.Parse(localhost), localport))
        {

        }
        public TcpPeer(IPAddress localhost, int localport) : this(new IPEndPoint(localhost, localport))
        {

        }

        public bool Connected { get => Socket == null ? false : Socket.Connected; }

        public IPEndPoint LocalEndPoint { get { return Socket.LocalEndPoint as IPEndPoint; } }

        public IPEndPoint RemoteEndPoint { get { return Socket.RemoteEndPoint as IPEndPoint; } }

        public void Bind(EndPoint endPoint)
        {
            Socket.Bind(endPoint);
        }

        public void Connect(EndPoint remote)
        {
            Socket.Connect(remote);
            isworking = true;
        }

        public void Connect(string host, int port)
        {
            this.Connect(new IPEndPoint(IPAddress.Parse(host), port));
        }

        /// <summary>
        /// 尝试连接到远端并指定连接超时时间
        /// </summary>
        public void TryConnect(string host, int port, int timeout = 2000)
        {
            TryConnect(new IPEndPoint(IPAddress.Parse(host), port), timeout);
        }
        /// <param name="remote"></param>
        /// <param name="timeout"></param>
        public void TryConnect(EndPoint remote, int timeout = 2000)
        {
            IAsyncResult result = Socket.BeginConnect(remote, null, null);

            bool success = result.AsyncWaitHandle.WaitOne(timeout, true);

            if (Socket.Connected)
            {
                Socket.EndConnect(result);
            }
            else
            {
                // NOTE, MUST CLOSE THE SOCKET

                Socket.Close();
                throw new TimeoutException("connect server TIMEOUT.");
            }
        }

        public virtual void Disconnect(bool reuse = false)
        {
            isworking = false;
            Socket.Disconnect(reuse);
            this.Disconnected?.Invoke(this, EventArgs.Empty);
        }

        public virtual void Close()
        {
            isworking = true;
            Socket?.Close();
            this.Disconnected?.Invoke(this, EventArgs.Empty);
        }

        public virtual void Dispose()
        {
            this.Close();
            Socket.Dispose();
            netstream?.Dispose();
            netstream = null;
        }


        public NetworkStream GetStream()
        {
            netstream = new NetworkStream(peersocket, true);
            return netstream;
        }

        public NetMessageStreamReader GetReader()
        {
            return new NetMessageStreamReader(GetStream());
        }

        public NetMessageStreamWriter GetWriter()
        {
            return new NetMessageStreamWriter(GetStream());
        }

        [DebuggerStepThrough]
        public int Receive(byte[] buffer, int offset, int size, SocketFlags flags)
        {
            return Socket.Receive(buffer, offset, size, flags);
        }
        [DebuggerStepThrough]
        public int Send(byte[] buffer, int offset, int size, SocketFlags flags)
        {
            return Socket.Send(buffer, offset, size, flags);
        }
        [DebuggerStepThrough]
        public async Task<int> SendAsync(byte[] buffer, int offset, int size, SocketFlags flags)
        {
            return await Task<int>.Run(
                new Func<int>(() =>
                {
                    return this.Send(buffer, offset, size, flags);
                })
                ); ;
        }

        public bool TestConnectionStatus()
        {
            try
            {
                this.Send(new byte[0], 0, 0, SocketFlags.None);
                return true;
            }
            catch (Exception )
            {//测试连接失败。
#if DEBUG
                Console.WriteLine($"[{this.RemoteEndPoint}]tcp客户端已断开连接。");
#endif
                return false;
            }
        }
        /// <summary>
        /// 使用指定的方法接收数据
        /// </summary>
        /// <param name="receiveAction"></param>
        public virtual void ReceiveWith(Action<TcpPeer, NetMessageStreamReader> receiveAction)
        {
            if (!isworking)
            {
                throw new Exception("当前peer尚未准备好，无法开始接收数据。");
            }
            Task.Run(
                delegate
                {
                    var reader = this.GetReader();
                    while (isworking && this.Connected)
                    {
                        receiveAction?.Invoke(this, reader);
                    }
                }
                );
        }
    }
}
